package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuInfoMapper;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuInfoService;
import entity.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.persistence.Id;
import java.util.*;

/**
 * @ClassName SkuServiceImpl
 * @Description
 * @Author Mr.L
 * @Date 11:50 2019/11/16
 * @Version 2.1
 **/
@Service
public class SkuInfoServiceImpl implements SkuInfoService {

    @Autowired
    private SkuInfoMapper skuInfohMapper;

    @Autowired(required = false)
    private SkuFeign skuFeign;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 检索
     *
     * @param searchMap
     * @return
     */
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        //构建检索条件（后期有多个条件检索，因此封装一个方法）
        NativeSearchQueryBuilder builder = builderBasicQuery(searchMap);
        //根据关键字检索
        Map<String, Object> resultMap = searchForPage(builder);
        // 查询分类
//        List<String> categoryList = searchCategoryList(builder);
//        resultMap.put("categoryList", categoryList);
//        //统计品牌列表
//        List<String> brandList = searchBrandList(builder);
//        resultMap.put("brandList", brandList);
//        //统计规格列表
//        Map<String, Set<String>> specMap = searchSpecMap(builder);
//        resultMap.put("specList", specMap);
        //获取所有分组结果
        Map<String, Object> map = groupList(builder);
        resultMap.putAll(map);
        return resultMap;
    }

    private Map<String, Object> groupList(NativeSearchQueryBuilder builder) {
        //多段分组
        builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(100));
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(100));
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(10000));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = aggregatedPage.getAggregations();

        //获取分组结果集
        List<String> brandList = groupResult(aggregations, "skuBrand");         //品牌分组
        List<String> categoryList = groupResult(aggregations, "skuCategory");   //分类分组
        List<String> specList = groupResult(aggregations, "skuSpec");           //规格分组
        Map<String, Set<String>> resultMap = pullMap(specList);

        //封装数据
        Map<String, Object> map = new HashMap<>();
        map.put("brandList",brandList);
        map.put("categoryList",categoryList);
        map.put("specList",resultMap);
        return map;
    }

    private List<String> groupResult(Aggregations aggregations, String groupName) {
        StringTerms stringTerms = aggregations.get(groupName);
        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
        List<String> list = new ArrayList<>();
        for (StringTerms.Bucket bucket : buckets){
            String value = bucket.getKeyAsString();
            list.add(value);
        }
        return list;
    }


    /**
     * 统计规格列表
     *
     * @param builder
     * @return
     */
    private Map<String, Set<String>> searchSpecMap(NativeSearchQueryBuilder builder) {
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = aggregatedPage.getAggregations();   //分组结果集
        //处理结果集
        StringTerms stringTerms = aggregations.get("skuSpec");
        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
        List<String> list = new ArrayList<>();
        for (StringTerms.Bucket bucket : buckets) {
            list.add(bucket.getKeyAsString());
        }
        //将集合数据转入map中
        Map<String, Set<String>> map = pullMap(list);
        return map;
    }

    /**
     * 封装list集合的规格数据
     *
     * @param list
     * @return
     */
    private Map<String, Set<String>> pullMap(List<String> list) {
        Map<String, Set<String>> map = new HashMap<>();
        //处理结果集
        for (String spec : list) {
            //将json转成map
            Map<String, String> specMap = JSON.parseObject(spec, Map.class);
            Set<Map.Entry<String, String>> entries = specMap.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                String key = entry.getKey();        //规格
                String value = entry.getValue();    //规格选项
                Set<String> set = map.get(key);     //判断是否有该规格
                if (set == null) {
                    set = new HashSet<>();
                }
                set.add(value);
                map.put(key, set);
            }
        }
        return map;
    }

    /**
     * 统计品牌列表的方法
     *
     * @param builder
     * @return
     */
    private List<String> searchBrandList(NativeSearchQueryBuilder builder) {
        //聚合查询
        builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = aggregatedPage.getAggregations();   //分组结果集
        //处理结果集
        StringTerms stringTerms = aggregations.get("skuBrand");
//        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();  可省略
        List<String> list = new ArrayList<>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            list.add(bucket.getKeyAsString());
        }
        return list;
    }


    /**
     * 将数据库数据写入到索引库中
     */
    @Override
    public void importSkuInfoToes() {
        //通过feign调用商品微服务接口
        Result<List<Sku>> result = skuFeign.findSkusByStatus("1");
        String text = JSON.toJSONString(result.getData());
        //将数据转成skuInfo
        List<SkuInfo> skuInfos = JSON.parseArray(text, SkuInfo.class);
        //处理动态字段
        for (SkuInfo skuInfo : skuInfos) {
            String spec = skuInfo.getSpec();
            Map<String, Object> specMap = JSON.parseObject(spec, Map.class);
            skuInfo.setSpecMap(specMap);
        }
        //将数据导入es中
        skuInfohMapper.saveAll(skuInfos);
    }


    /**
     * 分组查询
     *
     * @param builder
     * @return
     */
    private List<String> searchCategoryList(NativeSearchQueryBuilder builder) {
        //聚合查询
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = aggregatedPage.getAggregations();
        //处理分组结果集
        StringTerms stringTerms = aggregations.get("skuCategory");
        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
        List<String> list = new ArrayList<>();
        for (StringTerms.Bucket bucket : buckets) {
            list.add(bucket.getKeyAsString());
        }
        return list;
    }


    //根据条件查询并且封装数据
    private Map<String, Object> searchForPage(NativeSearchQueryBuilder builder) {
        HighlightBuilder.Field field = new HighlightBuilder.Field("name");  //对商品名称中包含的关键字进行高亮
        field.preTags("<font color='red'>");    //开始标签
        field.postTags("</font>");               //结束标签
        builder.withHighlightFields(field);     //添加高亮条件
        //根据条件（关键字、过滤的字段）查询
        NativeSearchQuery query = builder.build();
        //----------------上面的代码：设置检索条件----------------

        SearchResultMapper searchResultMapper = new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                //结果集处理
                SearchHits hits = response.getHits();
                //封装结果集
                List<SkuInfo> content = new ArrayList<>();
                for (SearchHit hit : hits){
                    String result = hit.getSourceAsString();    //json格式的字符串
                    SkuInfo skuInfo = JSON.parseObject(result, SkuInfo.class);  //普通结果集
                    HighlightField highlightField = hit.getHighlightFields().get("name");
                    if (highlightField != null){
                        Text[] texts = highlightField.getFragments();   //高亮的结果
                        String hightLightName = texts[0].toString();    //高亮的字段名
                        //将普通的名称替换成高亮的名称
                        skuInfo.setName(hightLightName);

                    }
                    content.add(skuInfo);
                }
                return new AggregatedPageImpl<>((List<T>) content, pageable, hits.getTotalHits());
            }
        };

        AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(query,SkuInfo.class, searchResultMapper);
//        NativeSearchQuery build = builder.build();
//        AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(build, SkuInfo.class);

        //结果集处理
        Map<String, Object> map = new HashMap<>();
        List<SkuInfo> rows = page.getContent();         //商品列表
        long totalElements = page.getTotalElements();   //总条数
        int totalPages = page.getTotalPages();          //总页数
        map.put("rows", rows);
        map.put("totalElements", totalElements);
        map.put("totalPages", totalPages);
        map.put("pageNum", builder.build().getPageable().getPageNumber()+1);  //当前页码
        map.put("pageSize", builder.build().getPageable().getPageSize());   //每页显示的条数
        return map;
    }

    /**
     * 封装检索条件
     *
     * @param searchMap
     * @return
     */
    private NativeSearchQueryBuilder builderBasicQuery(Map<String, String> searchMap) {
        //封装检索的条件
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //封装过滤的条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if ( searchMap != null && searchMap.size()>0 ) {
            //1、获取关键词
            String keywords = searchMap.get("keywords");
            if (!StringUtils.isEmpty(keywords)) {
                builder.withQuery(QueryBuilders.matchQuery("name", keywords));
            }

            //2、根据商品分类过滤
            String category = searchMap.get("category");
            if (!StringUtils.isEmpty(category)) {
                boolQuery.must(QueryBuilders.matchQuery("categoryName", category));
            }

            //3、根据品牌过滤
            String brand = searchMap.get("brand");
            if (!StringUtils.isEmpty(brand)) {
                boolQuery.must(QueryBuilders.matchQuery("brandName", brand));
            }

            //4、根据规格过滤
            Set<String> keys = searchMap.keySet();
            for (String key : keys) {
                if (key.startsWith("spec_")) {   //根据规格查询
                    String value = searchMap.get(key).replace("\\", "");
                    boolQuery.must(QueryBuilders.matchQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
                }
            }

            //5、价格区间段过滤
            String price = searchMap.get("price");
            if (!StringUtils.isEmpty(price)) {
                String[] priceArray = price.split("-");
                // [min - max]
                // >= 大于等于
                boolQuery.must(QueryBuilders.rangeQuery("price").gt(priceArray[0]));
                if (priceArray.length == 2) {
                    // <=,小于等于
                    boolQuery.must(QueryBuilders.rangeQuery("price").lte(priceArray[1]));
                }
            }

            //6、结果排序: 根据价格，销量，新品
            String sortRule = searchMap.get("sortRule");    //排序规则
            String sortField = searchMap.get("sortField");  //排序字段
            if (!StringUtils.isEmpty(sortRule)){
                /*if ("ASC".equals(sortRule)){
                    builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.ASC));
                }else {
                    builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.DESC));
                }*/
                builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
            }


        }
        builder.withFilter(boolQuery);  //过滤查询
        String pageNum = searchMap.get("pageNum");
        if (StringUtils.isEmpty(pageNum)){
            pageNum = "1";  //默认第一页
        }
        int page = Integer.parseInt(pageNum) - 1;   //起始行 = （当前页码 - 1） * 每页显示的条数
        int size = 40;   //每页显示的条数
        PageRequest pageable = PageRequest.of(page, size);
        builder.withPageable(pageable);     //分页查询
        return builder;
    }
}





